package cn.edu.bjtu.alex.rewrite;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.Map.Entry;


import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import cn.edu.bjtu.alex.rewrite.interfaces.Term;
import cn.edu.bjtu.alex.rewrite.interfaces.TermFeatureable;
import cn.edu.bjtu.entity.IDocument;
/**
 *	
 *
 * 这个类是模块之间解耦的关键,不同模块之间数据的传递就通过这个类<br>
 * 原来程序中,context将向量化数据,元信息放在不同的类中,更复杂,这里把它们放到一个类中.<br>
 * 主要的数据结果就是两个Map一个是正排索引,另外一个是倒排索引,把这两个结构表示的的含义明白了,整个过程就是围绕着这两个数据结构来进行的<br>
 * 注意:这里面有两个方法是synchronized的
 * 
 * @author alex
 *
 */
public class Context {
	
	private class TransformContext{
		public List<IDocument> documents;
		//<id,<word,term>>
		public Map<String,Map<String, Term>> segmentedDocuments;
		public TransformContext(){
			documents = new ArrayList<IDocument>();
			segmentedDocuments = new HashMap<>();
		}
		
	}
	public void addTransformDocument(IDocument d){
		tc.documents.add(d);
	}
	public void addTransformDocuments(List<IDocument> docs){
		this.tc.documents.addAll(docs);
	}
	public List<IDocument> getDocuments(){
		return tc.documents;
	}
	public IDocument getDocument(){
		return tc.documents.remove(0);
	}
	public void addTransformedDocument(IDocument d,Map<String, Term> terms){
		tc.segmentedDocuments.put(d.getID(), terms);
	}
	
	public Map<String,Map<String, Term>> getTransformedDocument(){
		return tc.segmentedDocuments;
	}
	public void clearSegmentedDocuments(){
		tc.segmentedDocuments.clear();
	}
	
	private TransformContext tc;
	
	public Context(String in,String out) {
		super();
		this.inputDir = in;
		this.outputDir = out;
		tc = new TransformContext();
		
	}
	
	//...
	private String inputDir;
	private String outputDir;
	
	public void reset(){
		clearSegmentedDocuments();
		labels.clear();
		labelledTotalDocCountMap.clear();
		termTable.clear();
		invertedTable.clear();
		idToLabelMap.clear();
		labelToIdMap.clear();
		featuredTerms.clear();
		totalDocCount = 0;
		m = null;
	}
	
	public String getInputDirString(){
		return this.inputDir;
	}
	public String getOutputDirString(){
		return this.outputDir;
	}
	public File getInputDir(){
		return new File(getInputDirString());
	}
	public File getOutputDir(){
		return new File(getOutputDirString());
	}
	
	public String getFileExtensionName(){
		return "txt";
	}
	public String getCharset(){
		return "utf-8";
	}
	public String getOutputVectorFileName(){
		return "vectors.txt";
	}
	public String getFeatureTermVectorFileName(){
		return "feature.txt";
	}
	public String getLabelVectorFileName(){
		return "label.txt";
	}
	/**
	 * 当TrainDriver运行时,获取锁
	 * 当TransDriver运行时,也会获取这个锁
	 * 所以,转化和建立不能同时进行
	 * 但是!
	 * 目前的写法两个转化也是不能同时执行的.
	 * 因为ctx.setOs(new ByteArrayOutputStream)这个是变量会有共享问题
	 * 怎么才能进行改呢,
	 * 1 把这个transLock更换成JUC包里面的读写锁
	 * 2 ctx.setOs那个方法里面使用ThreadLocal
	 * 
	 * 更换之前
	 * public Object transLock = new Object();
	 * 
	 * TFIDFExecuteContext.java
	 * @Override
		protected void fireInternal() {
			synchronized (ctx.transLock) {
				if(isNoCache()){
					this.ctx.reset();
				}
				new TrainDriver(this.ctx).fit();	
			}
		}

		TFIDFTransExecuteContext.java
		ByteArrayOutputStream os = new ByteArrayOutputStream();
			@Override
			protected void fireInternal() {
				ctx.setOs(os);
				synchronized (ctx.transLock) {
					new TransformDriver(this.ctx).transform();
				}
				
			}
			两个fireInternal是由不同线程调用的.所以会有共享问题
	 * 
	 */
	public ReadWriteLock transLock = new ReentrantReadWriteLock();
	
	
	//...
	private int totalDocCount = 0;
			
	private final List<String> labels = new ArrayList<String>();
	// Map<label, docCnt>
	private final Map<String, Integer> labelledTotalDocCountMap = Maps.newHashMap();
	//  Map<label, Map<doc ,Map<word, term>>>
	private final Map<String, Map<String, Map<String, Term>>> termTable = Maps.newHashMap();
	//  Map<word ,Map<label, Set<doc>>>
	private final Map<String, Map<String, Set<String>>> invertedTable = Maps.newHashMap();
	
	// <labelId, label>
	private final Map<Integer, String> idToLabelMap = Maps.newHashMap();
	// <label, labelId>
	private final Map<String, Integer> labelToIdMap = Maps.newHashMap();
	
	private Set<TermFeatureable> featuredTerms = Sets.newHashSet();
	

	//new view for setting term idf value;
	private Map<String,TermFeatureable> m = null;
	/**
	 * 这个方法是在调用fitinternal线程中被执行的,不存在同步问题
	 * @param label
	 */
	public void addLabel(String label) {
		if(!labels.contains(label)) {
			labels.add(label);
		}
	}
	/**
	 * TODO::marker
	 * 设调用fit方法的线程是Main线程,线程池里面的对每个类标下面的文档进行分词的线程是子线程
	 * addLabel是main线程在{@link cn.bjtu.edu.alex.rewrite.components.CollectWord.fitInternal}添加,一个label表示一个类,一个文件夹
	 * 如果一个文件夹下面是空的呢,只有子线程里面运行之后才能知道.所以,如果一个类下面是空的,就需要在子线程里面把这个label移除,所以这个方法必须是同步的
	 * 而addLabel里面只在一个线程操作,不需要同步
	 * @param label
	 */
	public void removeLabel(String label){
		synchronized (labels) {
			if(labels.contains(label)){
				labels.remove(label);
			}
		}
		System.out.println(label+" is removed because of zero documents");
	}
	
	
	public List<String> labels() {
		return Collections.unmodifiableList(labels);
	}
	
	
	public int totalDocCount() {
		return totalDocCount;
	}
	
	/**
	 * 搜集文档时,每个线程会调用addTerms和addTermToInvertedTabel
	 * @param totalDocCount
	 */
	public void setTotalDocCount(int totalDocCount) {
		this.totalDocCount = totalDocCount;
		
		
	}
	
	
	public void putLabelledTotalDocCount(String label, int labelledDocCount) {
		labelledTotalDocCountMap.put(label, labelledDocCount);
	}
	
	
	//////// inverted table ////////
	
	
	public synchronized void addTermToInvertedTable(String label, String doc, Term term) {
		String word = term.getWord();
		Map<String, Set<String>> labelledDocs = invertedTable.get(word);
		if(labelledDocs == null) {
			labelledDocs = Maps.newHashMap();
			invertedTable.put(word, labelledDocs);
		}
		Set<String> docs = labelledDocs.get(label);
		if(docs == null) {
			docs = Sets.newHashSet();
			labelledDocs.put(label, docs);
		}
		docs.add(doc);
	}
	
	
	public void addTermsToInvertedTable(String label, String doc, Map<String, Term> terms) {
		Iterator<Entry<String, Term>> iter = terms.entrySet().iterator();
		while(iter.hasNext()) {
			Entry<String, Term> entry = iter.next();
			addTermToInvertedTable(label, doc, entry.getValue());
		}
	}
	
	
	public int docCount(Term term) {
		String word = term.getWord();
		int count = 0;
		Map<String, Set<String>> labelledDocs = invertedTable.get(word);
		Iterator<Entry<String, Set<String>>> iter = labelledDocs.entrySet().iterator();
		while(iter.hasNext()) {
			count += iter.next().getValue().size();
		}
		return count;
	}
	
	
	public Iterator<Entry<String, Map<String, Set<String>>>> invertedTableIterator() {
		return invertedTable.entrySet().iterator();
	}
	
	//////// term table ////////
	
	
	public int termCount(String label, String doc) {
		int size = 0;
		// avoid empty file
		if(termTable.get(label) != null && termTable.get(label).get(doc) != null) {
			size = termTable.get(label).get(doc).size();
		}
		return size;
	}
	
	
	public synchronized void addTerms(String label, String doc, Map<String, Term> terms) {
		Map<String, Map<String, Term>> docs = termTable.get(label);
		if(docs == null) {
			docs = Maps.newHashMap();
			termTable.put(label, docs);
		}
		docs.put(doc, terms);
	}
	
	
	public int labelCount() {
		return termTable.keySet().size();
	}
	
	
	public Iterator<Entry<String, Map<String, Map<String, Term>>>> termTableIterator() {
		return termTable.entrySet().iterator();
	}
	
	//////// label vector map ////////
	
	// label->id
	
	public Iterator<Entry<String, Integer>> labelVectorMapIterator() {
		return labelToIdMap.entrySet().iterator();
	}
	
	
	public Integer getlabelId(String label) {
		return labelToIdMap.get(label);
	}
	
	
	public void putLabelToIdPairs(Map<String, Integer> globalLabelToIdMap) {
		this.labelToIdMap.putAll(globalLabelToIdMap);
	}
	
	// id->label
	
	public void putIdToLabelPairs(Map<Integer, String> globalIdToLabelMap) {
		this.idToLabelMap.putAll(globalIdToLabelMap);
	}
	
	
	public String getLabelById(Integer labelId) {
		return idToLabelMap.get(labelId);
	}
	
	//// featured vectors ////////
	
	
	public void setFeaturedTerms(Set<TermFeatureable> terms) {
		this.featuredTerms = terms;		
	}

	
	public Set<TermFeatureable> featuredTerms() {
		return Collections.unmodifiableSet(featuredTerms);
	}
	public Map<String,TermFeatureable> featuredTermsMap(){
		if(m==null){
			m = new HashMap<>();
			for(TermFeatureable tf :this.featuredTerms){
				m.put(tf.getWord(), tf);
			}
			
		}
		return m;
	}
	/**
	 * 这里为什么是OutputStream而不直接是ByteArrayOutputSteram
	 * 原来FeatrueProject中会调用ctx.setOs(res.getOutputStream())
	 * 这个类型不是ByteArrayOutputStream,会报错,主要是兼容老代码
	 */
	ThreadLocal<OutputStream> oses = new ThreadLocal<OutputStream>(){
		public OutputStream initialValue(){
			return new ByteArrayOutputStream(1000);
		}
//		public OutputStream get(){
//			ByteArrayOutputStream baos =(ByteArrayOutputStream)super.get();
//			
//			return baos;
//		}
	};

	public OutputStream getOs(){
		OutputStream os = oses.get();
	
		return oses.get();
	}
	/**
	 * @return the tc
	 */
	public TransformContext getTc() {
		return tc;
	}
	/**
	 * @param tc the tc to set
	 */
	public void setTc(TransformContext tc) {
		this.tc = tc;
	}
	/**
	 * @return the totalDocCount
	 */
	public int getTotalDocCount() {
		return totalDocCount;
	}
	/**
	 * @return the labels
	 */
	public List<String> getLabels() {
		return labels;
	}
	/**
	 * @return the labelledTotalDocCountMap
	 */
	public Map<String, Integer> getLabelledTotalDocCountMap() {
		return labelledTotalDocCountMap;
	}

	/**
	 * @return the termTable
	 */
	public Map<String, Map<String, Map<String, Term>>> getTermTable() {
		return termTable;
	}
	/**
	 * @return the invertedTable
	 */
	public Map<String, Map<String, Set<String>>> getInvertedTable() {
		return invertedTable;
	}
	/**
	 * @return the idToLabelMap
	 */
	public Map<Integer, String> getIdToLabelMap() {
		return idToLabelMap;
	}
	/**
	 * @return the labelToIdMap
	 */
	public Map<String, Integer> getLabelToIdMap() {
		return labelToIdMap;
	}
	/**
	 * @return the featuredTerms
	 */
	public Set<TermFeatureable> getFeaturedTerms() {
		return featuredTerms;
	}
	/**
	 * @param inputDir the inputDir to set
	 */
	public void setInputDir(String inputDir) {
		this.inputDir = inputDir;
	}
	/**
	 * @param outputDir the outputDir to set
	 */
	public void setOutputDir(String outputDir) {
		this.outputDir = outputDir;
	}
	/**
	 * @param os the os to set
	 */
	public void setOs(OutputStream os) {
		this.oses.set(os);
	}
	
	
	
	
	
	
	
}
